import path from "path";
import os from "os";
import { extend } from "quasar";
const { readFileSync } = require("fs");
import {
  app,
  BrowserView,
  BrowserWindow,
  dialog,
  ipcMain,
  Notification,
  screen,
} from "electron";
import logColor from "colors-console";

class MainElectron {
  // needed in case process is undefined under Linux
  platform = process.platform || os.platform();

  /**
   * @type {Record<NodeJS.Platform,Electron.BrowserWindowConstructorOptions>}
   */
  windowDefaultOptions = {
    darwin: {
      // titleBarStyle: "hidden",
      webPreferences: {
        nodeIntegration: true,
        contextIsolation: false,
      },
    },
    win32: {
      // titleBarStyle: "default",
      autoHideMenuBar: true,
      webPreferences: {
        nodeIntegration: true,
        contextIsolation: false,
      },
    },
  };

  /**
   * @type {Record<WindowName, BrowserWindowHandle>}
   */
  windowHandles = {};

  /**
   * 禁用预加载窗口
   * @type {boolean}
   */
  disableReloadWindow = false;

  /**
   * @type {Record<WindowName, BrowserViewHandle>}
   */
  viewHandles = {};

  /**
   * @type {string}
   */
  mainWindowName = "__main__";

  constructor() {
    const that = this;
    app.on("window-all-closed", () => {
      if (this.platform !== "darwin") {
        app.quit();
      }
    });
    app.on("before-quit", () => {
      // 禁用窗口预加载
      that.disableReloadWindow = true;
    });
  }

  logError(...args) {
    if (process.env.DEBUGGING) {
      let msg = "error:";
      if (typeof args[0] === "string") {
        msg = args[0];
        args.splice(args, 1);
      }
      console.log(logColor("red", msg), ...args);
    } else {
      dialog
        .showMessageBox({
          type: "error",
          buttons: ["确定"],
          title: "出错了",
          message: JSON.stringify(args),
          detail: "确定要退出" + app.name + "?",
        })
        .then((returnValue) => {
          if (returnValue.response === 0) {
            app.exit();
          }
        });
    }
  }

  /**
   *
   * @returns {string}
   */
  getDefaultIcon() {
    return path.resolve(__dirname, "icons/icon.png");
  }

  /**
   *
   * @returns {string}
   */
  getPublicFolder() {
    return path.resolve(__dirname, process.env.QUASAR_PUBLIC_FOLDER);
  }

  /**
   *
   * @returns {string}
   */
  getPreloadFile() {
    return path.resolve(__dirname, process.env.QUASAR_ELECTRON_PRELOAD);
  }

  /**
   *
   * @param {Electron.BrowserWindow | Electron.WebContents} context
   * @returns {boolean}
   */
  isDestroyed(context) {
    return !context || context.isDestroyed();
  }

  /**
   *
   * @param {string} name
   * @returns  {Electron.BrowserWindow}
   */
  getWindowByName(name) {
    const handle = this.windowHandles[name];
    if (!handle) {
      return null;
    }

    return handle.window;
  }

  /**
   *
   * @param {BrowserWindowOptions} options
   * @returns {Electron.BrowserWindowConstructorOptions}
   */
  filterWindowExtendOptions(options) {
    if (options.extendOption) {
      if (options.extendOption.windowName) {
        const win = this.getWindowByName(options.extendOption.windowName);
        if (!this.isDestroyed(win)) {
          options.parent = win;
        }
      }
    }
    return this.filterExtendOptions(options);
  }

  /**
   *
   * @param {BrowserViewOptions} options
   * @returns {Electron.BrowserViewConstructorOptions}
   */
  filterViewExtendOptions(options) {
    if (options.extendOption) {
    }
    return this.filterExtendOptions(options);
  }

  /**
   *
   * @param {BrowserViewOptions|BrowserViewOptions} options
   * @returns {Electron.BrowserViewConstructorOptions|Electron.BrowserViewConstructorOptions}
   */
  filterExtendOptions(options) {
    /** @type {BrowserViewOptions} */
    let opts = JSON.parse(JSON.stringify(options));
    if (!opts.extendOption) {
      return opts;
    }
    delete opts.extendOption;
    return opts;
  }

  /**
   *
   * @returns {Electron.BrowserWindowConstructorOptions}
   */
  getWindowDefaultOptions() {
    return this.windowDefaultOptions[this.platform] || {};
  }

  /**
   *
   * @returns {Electron.BrowserViewConstructorOptions}
   */
  getViewDefaultOptions() {
    return {};
  }

  deepExtend(...sources) {
    return extend(true, {}, ...sources);
  }

  /**
   * @param {BrowserWindowOptions} options
   * @returns {Promise<Electron.BrowserWindow>}
   */
  createMainWindow(options = {}) {
    /** @type {BrowserWindowOptions} */
    const opt = {
      icon: this.getDefaultIcon(),
      width: 1400,
      height: 800,
      minWidth: 1260,
      minHeight: 760,
      useContentSize: true,
      movable: true,
      show: true,
      webPreferences: {
        webviewTag: true,
        nodeIntegration: true,
        contextIsolation: true,
        preload: path.resolve(__dirname, process.env.QUASAR_ELECTRON_PRELOAD),
      },
    };
    return this.createWindow(
      this.mainWindowName,
      this.deepExtend(opt, options)
    );
  }

  /**
   *
   * @param {WindowName} name
   * @param {BrowserWindowHandle} handle
   */
  addWindowHandle(name, handle) {
    this.windowHandles[name] = handle;
  }

  /**
   *
   * @param {WindowName} name
   * @returns
   */
  removeWindowHandle(name) {
    const handle = this.windowHandles[name];
    if (!handle) {
      return;
    }
    if (!this.isDestroyed(handle.window)) {
      handle.options.reload = false;
      handle.window.destroy();
    }
    delete this.windowHandles[name];
  }

  /**
   *
   * @param {WindowName} name
   * @param {BrowserViewHandle} handle
   */
  addViewHandle(name, handle) {
    this.viewHandles[name] = handle;
    const extendOption = handle.options.extendOption || {};
    if (handle.window && extendOption.routePath) {
      handle.window.webContents.on(
        "did-navigate-in-page",
        handle.xhr.onNavigateInPage
      );
    }
  }

  /**
   *
   * @param {WindowName} name
   * @returns
   */
  removeViewHandle(name) {
    const handle = this.viewHandles[name];
    if (!handle) {
      return;
    }
    handle.window.webContents.off(
      "did-navigate-in-page",
      handle.xhr.onNavigateInPage
    );
    delete this.viewHandles[name];
  }

  /**
   *
   * @param {ContextExtendOptions} options
   * @param {Electron.WebContents} webCont
   */
  async loadContext(options, webCont) {
    // 设置cookie
    if (options.cookie) {
      console.log(logColor("green", "设置cookie"));
      this.setWebCookies(webCont, options.url, options.cookie);
    } else if (options.cookie === false) {
      this.clearWebCookie(webCont, options.url);
    }

    if (options.js) {
      // 注入JS
      this.executeJavaScript(
        webCont,
        this.readFileSync(options.js, this.getPublicFolder()).toString()
      );
    }

    if (options.css) {
      // 注入CSS
      this.insertCSS(
        webCont,
        this.readFileSync(options.css, this.getPublicFolder()).toString()
      );
    }
    await this.loadURL(webCont, options.url);

    // && process.env.DEBUGGING
    if (options.devtools) {
      this.openDevTools(webCont);
    }
  }

  /**
   *
   * @param {Electron.WebContents} webCont
   * @param {url} url
   * @returns
   */
  loadURL(webCont, url = "") {
    return new Promise((resolve, reject) => {
      if (url === false) {
        return;
      }
      if (this.isDestroyed(webCont)) {
        this.logError("webContents is destroyed", url);
        return reject("webContents is destroyed");
      }

      if (!url) {
        url = process.env.APP_URL;
      } else if (!require("url").parse(url).protocol) {
        url = process.env.APP_URL + "#" + url;
      }
      webCont
        .loadURL(url.replace(/\/$/, ""), { extraHeaders: "pragma: no-cache\n" })
        .catch(() => {});

      webCont.once("did-finish-load", () => resolve(true));
    });
  }
  /**
   * 设置页面cookies
   * @param {Electron.WebContents} webCont
   * @param {string} url
   * @param {CookieType} cookies
   */
  setWebCookies(webCont, url, cookies) {
    for (const name in cookies) {
      if (Object.hasOwnProperty.call(cookies, name)) {
        const value = cookies[name];
        webCont.session.cookies.set({
          url,
          domain: new URL(url).host.replace(/^.*?([^.]+\.[^.]+)$/, ".$1"),
          path: "/",
          name,
          value,
        });
      }
    }
  }

  /**
   *
   * @param {string} fileName
   * @param {string} dir
   * @param {Parameters<readFileSync>[1]} options
   * @returns
   */
  readFileSync(fileName, dir, options) {
    return readFileSync(
      path.join(dir || this.getPublicFolder(), fileName),
      options
    );
  }

  /**
   *
   * @param {Electron.WebContents} webCont
   * @param {string} css
   * @returns
   */
  insertCSS(webCont, css) {
    if (this.isDestroyed(webCont)) {
      this.logError("webContents 不存在或已销毁，无法插入CSS");
      return;
    }
    function listener() {
      webCont.insertCSS(css);
    }
    webCont.insertCSS(css);
    webCont.on("did-finish-load", listener);
  }

  /**
   *
   * @param {Electron.WebContents} webCont
   * @param {string} js
   * @returns
   */
  executeJavaScript(webCont, js) {
    if (this.isDestroyed(webCont)) {
      this.logError("webContents 不存在或已销毁，无法执行JS");
      return;
    }
    function listener() {
      webCont.executeJavaScript(js);
    }
    webCont.executeJavaScript(js);
    webCont.once("did-finish-load", () => {
      webCont.on("did-finish-load", listener);
    });
  }

  /**
   *
   * @returns {{currentDockState:'undocked'|'left'|'right'|'bottom',width:number,height:number}}
   */
  getUserDevtoolsInfo() {
    const fs = require("fs");
    const userDataPath = app.getPath("userData");
    const prefPath = path.join(userDataPath, "Preferences");
    let preferences = {};
    let splitViewState = {};
    let prefs;
    try {
      prefs = JSON.parse(fs.readFileSync(prefPath, "utf-8"));
      preferences = prefs.electron.devtools.preferences;
    } catch (error) {
      this.logError("getUserDevtoolsInfo fail:", error);
    }

    if (preferences) {
      try {
        splitViewState = JSON.parse(
          preferences["InspectorView.splitViewState"]
        );
      } catch (error) {
        this.logError("parse splitViewState fail:", error);
      }
    }
    let needSave = false;
    if (!splitViewState.vertical || !splitViewState.vertical.size) {
      splitViewState.vertical = { size: 340 };
      needSave = true;
    }
    if (!splitViewState.horizontal || !splitViewState.horizontal.size) {
      splitViewState.horizontal = { size: 280 };
      needSave = true;
    }
    if (needSave && prefs) {
      prefs.electron.devtools.preferences["InspectorView.splitViewState"] =
        JSON.stringify(splitViewState);
      fs.writeFileSync(prefPath, JSON.stringify(prefs));
    }
    if (preferences.currentDockState) {
      preferences.currentDockState = preferences.currentDockState.replaceAll(
        '"',
        ""
      );
    }

    return {
      currentDockState: preferences.currentDockState,
      width: parseInt(
        splitViewState.vertical ? splitViewState.vertical.size : 0
      ),
      height: parseInt(
        splitViewState.horizontal ? splitViewState.horizontal.size : 0
      ),
    };
  }

  /**
   * 打开devTools
   * @param {Electron.WebContents} webCont
   */
  openDevTools(webCont) {
    if (this.isDestroyed(webCont) || webCont.isDevToolsOpened()) {
      return;
    }
    webCont.openDevTools();
    const win = BrowserWindow.fromWebContents(webCont);
    if (this.isDestroyed(win)) {
      return;
    }
    const isResizable = win.isResizable();
    const size = win.getSize();
    win.setResizable(true);
    webCont.once("devtools-opened", (event) => {
      const devtoolsInfo = this.getUserDevtoolsInfo();
      if (
        devtoolsInfo.currentDockState &&
        devtoolsInfo.currentDockState !== "undocked"
      ) {
        let needCenter = false;
        const display = screen.getDisplayMatching(win.getBounds());
        let [width, height] = size;
        if (devtoolsInfo.currentDockState === "bottom") {
          height += devtoolsInfo.height;
        } else {
          width += devtoolsInfo.width;
        }
        if (width > display.workArea.width) {
          width = display.workArea.width;
          needCenter = true;
        }
        if (height > display.workArea.height) {
          height = display.workArea.height;
          needCenter = true;
        }
        win.setSize(width, height);
        if (needCenter) {
          win.center();
        }
      }
    });
    webCont.once("devtools-closed", () => {
      win.setSize(...size);
      win.setResizable(isResizable);
      win.center();
    });
  }

  /**
   *
   * @param {BrowserWindowOptions} options
   */
  parseWindowOptions(options) {}

  /**
   *
   * @param {BrowserWindowOptions} options
   * @returns {BrowserWindowHandle}
   */
  createWindowHandle(options = {}) {
    /** @type {BrowserWindowOptions} */
    const opt = this.deepExtend(this.getWindowDefaultOptions(), options);

    const extendOption = options.extendOption || {};
    delete opt.extendOption;

    if (extendOption.windowName) {
      const win = this.getWindowByName(extendOption.windowName);
      if (!this.isDestroyed(win)) {
        opt.parent = win;
      }
    }

    if (opt.webPreferences) {
      if (opt.webPreferences.preload) {
        opt.webPreferences.preload = this.getPreloadFile();

        /** @type {Electron.WebPreferences} */
        const webPreferences = {
          nodeIntegration: true,
          contextIsolation: true,
        };
        opt.webPreferences = this.deepExtend(
          webPreferences,
          opt.webPreferences
        );
      }
    }

    /** @type {BrowserWindowHandle} */
    const handle = {
      options,
      window: new BrowserWindow(this.filterWindowExtendOptions(opt)),
    };

    return handle;
  }

  /**
   *
   * @param {BrowserViewOptions} options
   * @returns {BrowserViewHandle}
   */
  createViewHandle(options) {
    /** @type {BrowserViewOptions} */
    const opt = this.deepExtend(this.getViewDefaultOptions(), options);
    // const extendOption = options.extendOption || {};
    const that = this;
    if (opt.webPreferences) {
      if (opt.webPreferences.preload) {
        opt.webPreferences.preload = this.getPreloadFile();

        /** @type {Electron.WebPreferences} */
        const webPreferences = {
          nodeIntegration: true,
          contextIsolation: true,
        };
        opt.webPreferences = this.deepExtend(
          webPreferences,
          opt.webPreferences
        );
      }
    }

    /**
     * @type {BrowserViewHandle}
     */
    const handle = {
      options,
      view: new BrowserView(this.filterViewExtendOptions(opt)),
      bounds: { width: 400, height: 400, x: 80, y: 80 },
      window: null,
      xhr: {
        show(bounds, url) {
          handle.window.addBrowserView(handle.view);
          handle.view.setBounds(bounds);
          handle.window.setTopBrowserView(handle.view);
          if (url) {
            that.loadURL(handle.view.webContents, url);
          }
        },
        hide() {
          // console.log("remove view", name);
          // handle.window.removeBrowserView(handle.view);
        },
        onNavigateInPage(e, url) {
          if (!opt.extendOption) {
            return;
          }
          if (url.indexOf(opt.extendOption.routePath) === -1) {
            handle.xhr.hide();
          }
        },
      },
    };

    return handle;
  }

  /**
   *
   * @param {WindowName} name
   * @param {BrowserViewOptions} options
   * @returns {Promise<Electron.BrowserView>}
   */
  async createView(name, options) {
    let handle = this.viewHandles[name];
    if (!handle || this.isDestroyed(handle.view.webContents)) {
      this.removeViewHandle(name);
      handle = this.createViewHandle(options);
      this.addViewHandle(name, handle);

      if (!options.extendOption) {
        options.extendOption = {};
      }

      await this.loadContext(options.extendOption, handle.view.webContents);

      const mainWindow = this.getMainWindow();
      handle.view.webContents.on(
        "ipc-message",
        function (event, channel, ...args) {
          mainWindow.webContents.send(channel, ...args);
        }
      );

      if (options.extendOption.reload) {
        handle.view.webContents.once("destroyed", () => {
          if (!this.disableReloadWindow && options.extendOption.reload) {
            console.log(
              name,
              "预加载的view关闭,重新预加载：",
              !this.disableReloadWindow
            );
            this.createView(name, handle.options);
          }
        });
      }
    }
    handle.options = options;
    return handle.view;
  }

  /**
   *
   * @param {WindowName} name
   * @param {BrowserWindowOptions} options
   * @returns {Promise<Electron.BrowserWindow>}
   */
  async createWindow(name, options) {
    let handle = this.windowHandles[name];
    if (!handle || this.isDestroyed(handle.window)) {
      this.removeWindowHandle(name);
      handle = this.createWindowHandle(options);
      this.addWindowHandle(name, handle);

      if (!options.extendOption) {
        options.extendOption = {};
      }

      await this.loadContext(options.extendOption, handle.window.webContents);

      if (options.extendOption.reload) {
        handle.window.once("closed", () => {
          if (!this.disableReloadWindow && options.extendOption.reload) {
            console.log(
              name,
              "预加载的窗口关闭,重新预加载：",
              !this.disableReloadWindow
            );
            this.createWindow(name, handle.options);
          }
        });
      }
    }
    handle.options = options;
    return handle.window;
  }

  /**
   * 操作窗口
   * 执行一些无返回的操作
   * @type {<R extends keyof Electron.BrowserWindow>(win:Electron.BrowserWindow,property:R,...args:Parameters<Electron.BrowserWindow[R]>)=>void}
   */
  operateWindow(win, property, ...args) {
    if (this.isDestroyed(win)) {
      this.logError("窗口已销毁或不存在，无法操作");
      return;
    }

    if (typeof win[property] === "function") {
      win[property](...args);
    }
  }

  /**
   *
   * @returns {Electron.BrowserWindow}
   */
  getMainWindow() {
    return this.getWindowByName(this.mainWindowName);
  }

  /**
   * 清除cookie
   * @param {Electron.WebContents} webCont
   * @param {string} url
   * @returns {Promise<boolean>}
   */
  clearWebCookie(webCont, url) {
    return new Promise((resolve) => {
      webCont.session.cookies.get({ url }).then((res) => {
        res.forEach((el) => {
          webCont.session.cookies.remove(url, el.name);
        });
        resolve(true);
      });
    });
  }

  /**
   * 获取cookie
   * @param {Electron.WebContents} webCont
   * @param {string} url
   * @param {?string} name
   * @returns {Promise<Electron.Cookie | Electron.Cookie[]>}
   */
  getWebCookie(webCont, url, name) {
    return new Promise((resolve) => {
      if (this.isDestroyed(webCont)) {
        this.logError("无法获取cookie,因为窗口不存在或已销毁");
        return;
      }
      if (!url) {
        url = webCont.getURL();
      }
      webCont.session.cookies.get({ url }).then((res) => {
        if (name) {
          resolve(res.find((v) => v.name === name));
          return;
        }
        resolve(res);
      });
    });
  }

  /**
   *
   * @param {Electron.WebContents} webCont
   */
  enableAutoUpdate(webCont) {
    const log = require("electron-log");
    const { autoUpdater } = require("electron-updater");
    const that = this;

    // 设置日志打印
    autoUpdater.logger = log;
    autoUpdater.logger.transports.file.level = "info";

    // 是否自动下载更新，设置为 false 时将通过 API 触发更新下载
    autoUpdater.autoDownload = false;
    // 是否允许版本降级，也就是服务器版本低于本地版本时，依旧以服务器版本为主
    autoUpdater.allowDowngrade = true;
    // 开发环境测试
    // autoUpdater.forceDevUpdateConfig = true;
    // 设置服务器版本最新版本查询接口配置
    // autoUpdater.setFeedURL({
    //   provider: "keygen",
    //   account: "24115c53-b8f6-40fa-99fe-68d84b3a05dc",
    //   product: "fd2aa613-2d6e-4b98-a3d0-d5f4c731c363",
    //   channel: "latest",
    // });

    // 保存是否需要安装更新的版本状态，因为需要提供用户在下载完成更新之后立即更新和稍后更新的操作
    let NEED_INSTALL = false;

    async function checkUpdate() {
      let UpdateCheckResult;
      try {
        // UpdateCheckResult = await autoUpdater.checkForUpdates();
        // log.info(UpdateCheckResult, autoUpdater.currentVersion.version);
      } catch (error) {
        // if (!that.isDestroyed(webCont)) {
        //   webCont.send("update", error);
        // }
      }

      // 判断版本不一致，强制更新
      setTimeout(checkUpdate, 5000);
      if (!that.isDestroyed(webCont)) {
        webCont.send("update", 123);
      }
      if (!UpdateCheckResult) {
        return;
      }

      const needUpdate =
        UpdateCheckResult.updateInfo.version !==
        autoUpdater.currentVersion.version;

      if (needUpdate) {
        const result = {
          needUpdate,
          currentVersion: autoUpdater.currentVersion,
          updateInfo: UpdateCheckResult.updateInfo,
        };
        if (!that.isDestroyed(webCont)) {
          webCont.send("update", result);
        }
        return result;
      }
    }

    // API 触发更新下载
    function startDownload(callback) {
      function output(type, ...args) {
        callback && callback instanceof Function && callback(type, ...args);
      }
      // 检测开始
      autoUpdater.on("checking-for-update", function () {
        output("checking-for-update");
      });
      // 更新可用
      autoUpdater.on("update-available", function () {
        output("update-available");
      });
      // 更新不可用
      autoUpdater.on("update-not-available", function () {
        output("update-not-available");
      });
      // 监听下载进度并推送到更新窗口
      autoUpdater.on("download-progress", (data) => {
        output("download-progress", data);
      });
      // 监听下载错误并推送到更新窗口
      autoUpdater.on("error", (err) => {
        output("error", err);
      });
      // 监听下载完成并推送到更新窗口
      autoUpdater.on("update-downloaded", () => {
        NEED_INSTALL = true;
        output("update-downloaded");
      });
      // 下载更新
      try {
        autoUpdater.downloadUpdate();
      } catch (err) {
        output("error", err);
      }
    }

    ipcMain.handle("checkUpdate", () => {
      return checkUpdate();
    });

    // 监听应用层发送来的进程消息，开始下载更新
    ipcMain.handle("startDownload", (event) => {
      startDownload((type, ...args) => {
        event.sender.send(type, ...args);
      });
    });

    ipcMain.handle("getCurrentVersion", () => autoUpdater.currentVersion);

    // 监听用户点击更新窗口立即更新按钮进程消息
    ipcMain.handle("quitAndInstall", (event) => {
      NEED_INSTALL = false;
      try {
        autoUpdater.quitAndInstall();
        return Promise.resolve(true);
      } catch (error) {
        return Promise.reject(error);
      }
    });

    // 用户点击稍后安装后程序退出时执行立即安装更新
    app.on("will-quit", () => {
      console.log("安装更新", NEED_INSTALL);
      if (NEED_INSTALL) {
        autoUpdater.quitAndInstall();
      }
    });
    if (app.isPackaged) {
    }
    checkUpdate();
  }
}

export default MainElectron;
